package org.NooLab.itexx.co;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import org.NooLab.itexx.ITexx;
import org.NooLab.itexx.ITexxFactory;
 
import org.NooLab.itexx.comm.tcp.box.TcpBox;
import org.NooLab.itexx.tcp.ClientMsgHandlerIntf;
import org.NooLab.itexx.tcp.CommBox;
import org.NooLab.itexx.tcp.TcpClient;
import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.files.StopFileWatcher;
import org.NooLab.utilities.logging.LogControl;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.GUID;
import org.NooLab.utilities.strings.StringsUtil;
import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;


/**
 * 
 * 
 * starts the applications ....
 * 
 * arranges udp ports for basic communication (being-alive ping, stopping, restarting)
 * and maps the threads to the processes
 * 
 * This should read a resource XML for instantiation the various sub processes
 * 
 * see the jar path org.NooLab.itexx.co.resources ;
 * 
 */
public class CoWorkers 
						implements 
									Observer,
									ProcessCtrlIntf , 
									ClientMsgHandlerIntf 
									{

	ArrayList<CoWorker> items = new ArrayList<CoWorker> (); 
	
	ITexxFactory itxFactory ;
	boolean waitForFeedback = true;
	

	BidiMap ports = new DualHashBidiMap();
	int lastPort = CoWorkerIntf._mainServerPort+10;  
	
	ArrayList<ProcessLauncherProcess> launcherProcesses = new ArrayList<ProcessLauncherProcess>();
	
	PrintLog out = new PrintLog(2, false);
	public DFutils fileutil = new DFutils();
	public StringsUtil strgutil = new StringsUtil();

	// ========================================================================
	public CoWorkers( ITexxFactory factory ){
		itxFactory = factory;
		
	}
	// ========================================================================
	
	public void launch(int periodgap, boolean waitforFeedback) {
		
		CoWorker p;
		
		waitForFeedback = waitforFeedback;
		
		
		out.print(2, "launching processes...") ;
		// all items
		for (int i=0;i<items.size();i++){
			
			p = items.get(i) ;
			p.setStatus( CoWorkerIntf._CO_STATUS_STARTING );
			
			if (LogControl.Level >=2)
			{
				p.showArguments();
			}
			
			ProcessLauncherProcess plp = new ProcessLauncherProcess( p );
			launcherProcesses.add(plp);
			plp.start();
			
			out.delay(periodgap);
			// is it running? some keyword in the console output?
			// note, we will show the output only if there is an error, we buffer 50 + (200 * level) rows...
			
		}// ->
	}

	public void releaseFollowUp(CoWorker p) {
		CoWorker coItem ;
		CoWorker px;
		
		if (p==null){
			// even a complete shutdown ?? 
			return;
		}
		
		for (int i=0;i<items.size();i++){
			
			coItem = items.get(i) ;
			
			if ((coItem == p) && (i<items.size()-1)){
				px = items.get(i+1); 
				px.sendRelease( px.proposedPort );
				break;
			}
		} // i->
		
		
	}

	public int size(){
		return items.size() ;
	}
	
	public  ArrayList<CoWorker> getItems(){
		return items ;
	}
	
	public int GetConcurrentCountOf(CoWorker processDef) {
		// 
		int ccount = 0;
		CoWorker p;
		
		for (int i=0;i<items.size();i++){
			p = items.get(i) ;
			
			if ((p.jarFilename.contentEquals( processDef.jarFilename)) && 
				(p.mainClass.contentEquals(processDef.mainClass))){
				ccount++;
			}
			
		}// i->
		
		
		return ccount;
	}

	public CoWorker getCoWorkerBySecret(String secret) {
		// 
		CoWorker p=null, _p;
		
		for (int i=0;i<items.size();i++){
			
			_p = items.get(i) ;
			
			if (_p.instanceGuid.contentEquals( secret )){
				p = _p;
				break;
			}
			
		}// i->
		
		
		return p;
	}

	public int getNextPort(String instanceGuid) {
		 
		int i,dummyreceiver;
		int start = lastPort+2;  
		boolean found=false, hb;
		
		CommBox commBox = new CommBox();
		 
		
		
		dummyreceiver = CoWorkerIntf._mainServerPort;
		
		i=start;
		while ((i<20000) && (found==false)){
			
			if (( i > CoWorkerIntf._mainServerPort+1) && 
				( ports.containsValue(start+i)==false)){
				
				hb = commBox.checkPortAvailability( i, dummyreceiver);
				
				if (hb){
					found=true;
					lastPort = i;
					ports.put( instanceGuid, lastPort);
					break;
				} // port available ?
			}

			i++;
			
		}
		
		return lastPort;
	}
	public CoWorker getCoWorker( ITexx itexx){
		
		
		CoWorker coworks = new CoWorker(this,itexx);
		coworks.setGuid(GUID.randomvalue()) ;
		
		return coworks;
	}
	
	public void add( CoWorker coworks){
		items.add( coworks ) ;
		// later we will sort this along ".startingOrder"
	}

	
	class ProcessLauncherProcess implements Runnable{
		
		Thread plpThrd;
		CoWorker coWorker;
		
		public ProcessLauncherProcess(CoWorker cowo){
			coWorker = cowo ;
			plpThrd = new Thread(this,"plpThrd") ; 
		}
		
		public void start(){
			plpThrd.start() ;
		}
	

		@Override
		public void run() {
			
			try {
				
				coWorker.launch() ;
				
			} catch (Exception e) {
				// show name of process
				String estr = "";
				if (coWorker!=null){
					estr = "Name of failing coworker process: "+coWorker.name;
				}
				out.printlnErr(1, "\n"+estr+"\n");
				e.printStackTrace();
			}
		}
	}



	class ProcessStoppingProcess implements Runnable{
		
		Thread   pspThrd;
		CoWorker coWorker;
		
		public ProcessStoppingProcess (CoWorker cowo){
			coWorker = cowo ;
			pspThrd = new Thread(this,"pspThrd") ;
			pspThrd.start() ;
		}
		
		public void start(){
			pspThrd.start() ;
		}
	

		@Override
		public void run() {
			
			try {
				
				stop(coWorker) ;
				
			} catch (Exception e) {
				// show name of process
				String estr = "";
				if (coWorker!=null){
					estr = "Coworker process that probably failed to get stopped: "+coWorker.name;
				}
				out.printlnErr(1, "\n"+estr+"\n");
				e.printStackTrace();
			}
		}
	}

	
	public void stop() throws Exception {
		 
		//  opening a thread for each of the processes, then sending a "terminate", waiting, and then killing
		//  using stop-files : filename = [process-class].stop  in home directory
		out.print(2, "...going to stop co-worker processes...");
		for (int i=0;i<items.size();i++){
			// send
			CoWorker p = items.get(i) ;
			
			new ProcessStoppingProcess(p);
			
		}
		
		// removing all .stop files
	}

	public void stop(CoWorker p) throws Exception {
		
		if (p.getProcess()!=null){
			
			out.print(2, "...stopping process <"+p.getName()+"> ");
			
			sendTcpStopMessage(p);
			writeStopFile(p);
			
			p.getProcess().destroy();
		}
		
	}
	
	private void writeStopFile(CoWorker p) throws Exception {
		
		String path,filename,fname,appsecret ;
		
		
		
		appsecret = p.getArgumentByTopic("appsecret");
		appsecret = appsecret.replace("-appsecret", "").trim();
		
		path = p.appWorkingDir ;
		fname = strgutil.removeFileExtension(p.jarFilename, ".jar");
		fname = strgutil.removeFileExtension(fname, "jar");
		
		filename =  fname + ".stop";
		filename = fileutil.createpath( path, filename);
		
		if (DFutils.folderExists(p.appWorkingDir)){
			fileutil.writeFileSimple(filename, appsecret);
		}



		
	}
	
	
	private void sendTcpStopMessage(CoWorker p) {

		String encstr="" ;
		
		// using a simple tcp client here...
		 TcpClient tcp = new TcpClient(7202,7201,this,""); // TODO make this dynamic, iTexx knows about all CoWorkers' ports !
		// int remoteport, int ctrlport, ClientMsgHandlerIntf clientmsg, String description
		
		tcp.send(encstr);
		out.delay(500);
		tcp.close(1);

	}

	@Override
	public void update(Observable sendObj, Object msgObj) {
		 
		String senderName = sendObj.getClass().getSimpleName();
		// we send it back 
		out.print(3, "msg from <"+(sendObj.getClass().getSimpleName())+">");
		out.print(3, " ... received by iTexx (c): "+((String) msgObj)) ;
		if ((senderName.contains("StopFile")) && ((String) msgObj).contains("StopFile")){
			try {
				stop();
			} catch (Exception e) {				  
			} 
			out.delay(300);
			out.printErr(2, "iTexx (c) has been shut down.\n");
			System.exit(1);
		}
	}
	
}
